import numpy
import re
from netCDF4  import Dataset as netCDF4_Dataset
from operator import mul
from os.path  import abspath as os_path_abspath
from ..ancillaryvariables import AncillaryVariables
from ..coordinate         import Coordinate
from ..coordinatebounds   import CoordinateBounds
from ..cellmeasure        import CellMeasure
from ..space              import Space
from ..transform          import Transform
from ..field              import Field
from ..cellmethods        import CellMethods
from ..fieldlist          import FieldList
from ..data               import Data
from ..units              import Units
from .functions import _open_netCDF_file
from .filearray import NetCDFFileArray

def read(filename, only_lama=False, not_lama=False):
    ''' 

Read fields from an input netCDF file on disk or from an OPeNDAP
server location.

The file may be big or little endian.

NetCDF dimension names are stored in the `nc_dimensions` attribute of
a field's space and netCDF variable names are stored in the `ncvar`
attributes of the field and its space components (coordinates,
coordinate bounds, cell measures and transformations).

:Parameters:

    filename : str or file
        A string giving the file name or OPenDAP URL, or an open file
        object, from which to read fields. Note that if a file object
        is given it will be closed and reopened.

:Returns:

    out : FieldList
        The fields in the file.

**Examples**

>>> f = cf.netcdf.read('file.nc')
>>> type(f)
<class 'cf.field.FieldList'>
>>> f
[<CF Field: pmsl(30, 24)>,
 <CF Field: z-squared(17, 30, 24)>,
 <CF Field: temperature(17, 30, 24)>,
 <CF Field: temperature_wind(17, 29, 24)>]

>>> cf.netcdf.read('file.nc')[0:2]
[<CF Field: pmsl(30, 24)>,
 <CF Field: z-squared(17, 30, 24)>]

>>> cf.netcdf.read('file.nc', units='K')
[<CF Field: temperature(17, 30, 24)>,
 <CF Field: temperature_wind(17, 29, 24)>]

>>> cf.netcdf.read('file.nc')[0]
<CF Field: pmsl(30, 24)>

'''
    if isinstance(filename, file):
        name = filename.name
        filename.close()
        filename = name
    #--- End: if
    
    filename = _abspath(filename)

    # Read the netCDF file 
    nc = _open_netCDF_file(filename) 

    # Set of all of the netCDF variable names in the file.
    #
    # For example:
    # >>> variables
    # set(['lon','lat','tas'])
    variables = set(map(str, nc.variables))

    # ----------------------------------------------------------------
    # Put the file's global attributes into the global
    # 'global_attributes' dictionary
    # ----------------------------------------------------------------
    global_attributes = {}
    for attr in map(str, nc.ncattrs()):
        try:
            value = nc.getncattr(attr)
            if isinstance(value, basestring):
                try:
                    global_attributes[attr] = str(value)
                except UnicodeEncodeError:
                    global_attributes[attr] = value.encode(errors='ignore')          
            else:
                global_attributes[attr] = value     
        except UnicodeDecodeError:
            pass
    #--- End: for
    
    lama = ('Conventions' in global_attributes and 
            'LAMA' in global_attributes['Conventions'])

    if (only_lama and not lama) or (not_lama and lama):
        print 'WEE'
        return FieldList()
    #--- End: if  

    # ----------------------------------------------------------------
    # Create a dictionary keyed by nc variable names where each key's
    # value is a dictionary of that variable's nc
    # attributes. E.g. attributes['tas']['units']='K'
    # ----------------------------------------------------------------
    attributes = {}
    for ncvar in variables:
        attributes[ncvar] = {}
        for attr in map(str, nc.variables[ncvar].ncattrs()):
            try:
                attributes[ncvar][attr] = nc.variables[ncvar].getncattr(attr)
                if isinstance(attributes[ncvar][attr], basestring):
                    try:
                        attributes[ncvar][attr] = str(attributes[ncvar][attr])
                    except UnicodeEncodeError:
                        attributes[ncvar][attr] = attributes[ncvar][attr].encode(errors='ignore')
            except UnicodeDecodeError:
                pass
        #--- End: for

        # Check for bad units
        if 'units' in attributes[ncvar]:
            # Check for bad units
            try:
                Units(units=attributes[ncvar]['units'])
            except ValueError:
                # Units in file have been set to unknown units so 1)
                # give a warning, 2) set the 'nonCF_units' property to
                # the bad units and 3) remove the offending units.
                print(
"WARNING: Moving unsupported units to 'nonCF_units': '%s'" %
attributes[ncvar]['units'])
                attributes[ncvar]['nonCF_units'] = attributes[ncvar].pop('units')
        #--- End: if
    
        # Check for bad calendar
        if 'calendar' in attributes[ncvar]:
            try:
                Units(calendar=attributes[ncvar]['calendar'])
            except ValueError:
                # Calendar in file has been set to unknown calendar so
                # 1) give a warning, 2) set the 'nonCF_calendar'
                # property to the bad calendar and 3) remove the
                # offending calendar.
                print(
"WARNING: Moving unsupported calendar to 'nonCF_calendar': '%s'" %
attributes[ncvar]['calendar'])
                attributes[ncvar]['nonCF_calendar'] = attributes[ncvar].pop('calendar')
    #--- End: for

    # ----------------------------------------------------------------
    # Remove everything bar data variables from the list of
    # variables. I.e. remove dimension and auxiliary coordinates,
    # their bounds and grid_mapping variables
    # ----------------------------------------------------------------
    for ncvar in variables.copy():

        # Remove dimension coordinates and their bounds
        if ncvar in map(str, nc.dimensions):

            if ncvar in variables:
                variables.discard(ncvar)
                for attr in ('bounds', 'climatology'):
                    if attr not in attributes[ncvar]:
                        continue
                
                    # Check the dimensionality of the coordinate's
                    # bounds. If it is not right, then it can't be a
                    # bounds variable and so promote to an independent
                    # data variable
                    bounds = attributes[ncvar][attr]
                    if bounds in nc.variables:
                        if nc.variables[bounds].ndim == nc.variables[ncvar].ndim+1:
                            variables.discard(bounds)
                        else:
                            del attributes[ncvar][attr]

                        break
                    else:
                        del attributes[ncvar][attr]
                        print(
"WARNING: Missing bounds variable '%(bounds)s' in %(filename)s" %
locals())
                #--- End: for
            #--- End: if

            continue
        #--- End: if

        # Still here? Then remove auxiliary coordinates and their
        # bounds
        if 'coordinates' in attributes[ncvar]:
            # Allow for (incorrect) comma separated lists
            for aux in re.split('\s+|\s*,\s*', attributes[ncvar]['coordinates']):
                if aux in variables:
                    variables.discard(aux)
                    for attr in ('bounds', 'climatology'):
                        if attr not in attributes[aux]:
                            continue

                        # Check the dimensionality of the coordinate's
                        # bounds. If it is not right, then it can't be
                        # a bounds variable and so promote to an
                        # independent data variable.
                        bounds = attributes[aux][attr]
                        if bounds in nc.variables:
                            if nc.variables[bounds].ndim == nc.variables[aux].ndim+1:
                                variables.discard(bounds)
                            else:
                                del attributes[aux][attr]

                            break
                        else:
                            del attributes[aux][attr]
                            print(
"WARNING: Missing bounds variable '%(bounds)s' in %(filename)s" %
locals())
                    #--- End: for
                #--- End: if
            #--- End: for
        #--- End: if
    
        # Remove grid mapping variables
        if 'grid_mapping' in attributes[ncvar]:
            variables.discard(attributes[ncvar]['grid_mapping'])

        # Remove cell measure variables
        if 'cell_measures' in attributes[ncvar]:
            cell_measures = re.split('\s*(\w+):\s*',
                                     attributes[ncvar]['cell_measures'])
            for ncvar in cell_measures[2::2]:
                variables.discard(ncvar)
        #--- End: if

    #--- End: for

    # ----------------------------------------------------------------
    # Everything left in the variables set is now a proper data
    # variable, so make a list of fields, each of which contains one
    # data variable and the relevant shared metadata.
    # ----------------------------------------------------------------

    # Dictionary mapping netCDF variable names of space components to
    # their cf Variables.
    #
    # For example:
    # >>> seen_in_file
    # {'lat': <CF Coordinate: (73)>}
    seen_in_file = {}

    # Dictionary of ?
    #
    # For example:
    # >>> formula_terms
    #
    formula_terms = {}

    # Set
    #
    # For example:
    # >>> 
    #
    not_fields = set()

    ancillary_variables = set()
    fields_in_file      = FieldList()

    for data_var in variables:

        # Don't turn LAMA variables into fields
        if _is_lama_variable(nc.variables[data_var], lama):
            continue

        f = _create_Field(filename,
                          nc,
                          data_var,
                          attributes,
                          seen_in_file,
                          formula_terms, 
                          ancillary_variables, 
                          not_fields,
                          global_attributes,
                          lama=lama)    
        
        fields_in_file.append(f)
    #--- End: for

    # ----------------------------------------------------------------
    # If any transform equation terms are fields then find out which
    # fields they are
    # ----------------------------------------------------------------
    if formula_terms:

        # Find the formula terms fields, identified by their netCDF
        # variable names.
        ft_ncvars = set()
        for transform in formula_terms.itervalues():
            for value in transform.itervalues():
                if isinstance(value, dict):
                    ncvar = value['ncvar']
                    ft_ncvars.add(ncvar)
        #--- End: for

        # 
        ncvar_to_field = {}
        i = 0
        while ft_ncvars:
            try:
                f = fields_in_file[i]
            except IndexError:
                # No more fields
                break

            # Still here?
            ncvar = f.ncvar            
            if ncvar in ft_ncvars:
                # ----------------------------------------------------
                # This field is being used as a transform field
                # ----------------------------------------------------
                # Finalize the field
                f.finalize()
                # Remove formula_terms transforms from fields
                # contained within formula_terms transforms
                transforms = f.space.transforms
                for key in transforms.keys():
                    if transforms[key].isformula_terms:
                        del transforms[key]
                        for coord in f.space.itercoordinates():
                            if hasattr(coord, 'transforms'):
                                del coord.transforms
                #--- End: for
                ncvar_to_field[ncvar] = f
                fields_in_file.pop(i)
                # Make sure that the field is not in the list of
                # variables which have been turned into auxiliary
                # coordinates
                not_fields.discard(ncvar)
            else:
                i += 1
        #--- End: while

        # Remove fields which have been turned into auxiliary
        # coordinates and have not already been removed as transform
        # fields.
        if not_fields:
            ncvars = fields_in_file.ncvar
            for ncvar in not_fields:
                try:
                    i = ncvars.index(ncvar)
                except ValueError:
                    pass
                else:
                    ncvars.pop(i)
                    fields_in_file.pop(i)                    
        #--- End: if

        for f in fields_in_file:

            # Loop round this field's transforms (if any)
            transforms = f.space.transforms.values()
            for transform in transforms:
                if transform.isgrid_mapping:
                    continue

                # Still here? Then replace any pointers to fields with
                # the actual fields.
                for term, value in transform.iteritems():
                    if isinstance(value, dict):
                        transform[term] = ncvar_to_field[value['ncvar']].copy()
                        
                #--- End: for
            #--- End: for
        #--- End: for

    #--- End: if
        
    # ----------------------------------------------------------------
    # For each field that has ancillary variables, replace its
    # built-in list of netCDF variable names with an
    # AncillaryVariables object.
    # ----------------------------------------------------------------
    if ancillary_variables:
        ncvar_to_field = {}
        index = 0
        while ancillary_variables:
            try:
                f = fields_in_file[index]
            except IndexError:
                # No more fields
                break
            
            # Still here?
            ncvar = f.ncvar            
            if ncvar in ancillary_variables:
                # ----------------------------------------------------
                # This field is being used as an ancillary variable
                # field
                # ----------------------------------------------------
                # Finalize the field
                f.finalize()
                ancillary_variables.discard(ncvar)
                ncvar_to_field[ncvar] = f
                fields_in_file.pop(index)
            else:
                index += 1
        #--- End: while
        
        for f in fields_in_file:
            if not hasattr(f, 'ancillary_variables'):
                continue

            av = AncillaryVariables()
            for ncvar in f.ancillary_variables:
                av.append(ncvar_to_field[ncvar].copy())

            f.ancillary_variables = av
        #--- End: for
    #--- End: if

    # Finalize the fields
    for f in fields_in_file:
        print '\n^^^^^^^^^^^^^^^^^^^^^^^finalizing', repr(f)
        f.finalize()

    return fields_in_file
#--- End: def

def _create_Field(filename,
                  nc,
                  data_var,
                  attributes,
                  seen_in_file,
                  formula_terms, 
                  ancillary_variables, 
                  not_fields,
                  global_attributes,
                  lama=False):
    '''

Create a field for a given netCDF variable.

:Parameters:

    filename : str
        The name of the netCDF file.

    nc : netCDF4.Dataset
        The entire netCDF file in a `netCDF4.Dataset` instance.

    data_var : str
        The netCDF name of the variable to be turned into a field.

    attributes : dict
        Dictionary of the data variable's netCDF attributes.

    seen_in_file : dict

    formula_terms : dict

    ancillary_variables : set

    global_attributes : dict

:Returns:

    out : Field
        The new field.

'''
    # Add global attributes to the data variable's properties, unless
    # the data variables already has a property with the same name.
    for attr, value in global_attributes.iteritems():
        if attr not in attributes[data_var]:
            attributes[data_var][attr] = value

    # Take cell_methods out of the data variable's properties since it
    # will need special processing once the space has been defined
    if 'cell_methods' in attributes[data_var]:
        cell_methods = CellMethods(attributes[data_var].pop('cell_methods'))
    else:
        cell_methods = None

    # Take add_offset and scale_factor out of the data variable's
    # properties since they will be dealt with by the variable's Data
    # object
    for attr in ('add_offset', 'scale_factor'):
        if attr in attributes[data_var]:
            del attributes[data_var][attr]

    # Change numpy arrays to tuples for selected attributes
    for attr in ('valid_range',):
        if attr in attributes[data_var]:
            attributes[data_var][attr] = tuple(attributes[data_var][attr])

    field_ncdimensions = _ncdimensions(nc.variables[data_var], lama)
    
    # ----------------------------------------------------------------
    # Initialize the field with the data variable and its attributes
    # ----------------------------------------------------------------
    f = Field(attributes[data_var])

    f.ncvar = data_var
    f.file = filename

    f.space = Space()

#    if 'units' not in attributes[data_var]:
#        f.Units = Units()

    # Map netCDF variable and dimension names to coordinates.
    # 
    # For example:
    # >>> ncvar_to_coord
    # {'geo_region':  <CF Coordinate:...>}
#    ncvar_to_coord = {}

    # Map netCDF dimension dimension names to space dimension names.
    # 
    # For example:
    # >>> ncdim_to_dim
    # {'lat': 'dim0', 'time': 'dim1'}
    ncdim_to_dim = {}

    ncvar_to_key = {}
        
    f.space.dimensions['data'] = []
    f.space.nc_dimensions      = {}
    
    # ----------------------------------------------------------------
    # Add dimensions and dimension coordinates to the field
    # ----------------------------------------------------------------
    for ncdim in field_ncdimensions: #map(str, nc.variables[data_var].dimensions):
        
        dim = f.space.new_dimension(size=len(nc.dimensions[ncdim]))

        if ncdim in nc.variables:
            # There is a dimension coordinate for this dimension, so
            # create the coordinate and the dimension.
            if ncdim in seen_in_file:
                coord = seen_in_file[ncdim].copy()
            else:
                coord = _create_Coordinate(nc, ncdim, attributes, f,
                                           lama=lama, key=dim)
                seen_in_file[ncdim]   = coord                
            #--- End: if

            f.space.insert_dim_coordinate(coord, key=dim, copy=False)

            ncvar_to_key[ncdim] = dim
        #--- End: if

        # Update data dimension name and set dimension size
        f.space.nc_dimensions[dim] = ncdim
        f.space.dimensions['data'].append(dim)
        
        ncdim_to_dim[ncdim] = dim
    #--- End: for

    f.Data = _set_Data(nc.variables[data_var], f, f, lama=lama, key='data')

    # ----------------------------------------------------------------
    # Add scalar dimension coordinates and auxiliary coordinates to
    # the field
    # ----------------------------------------------------------------
    coordinates = f.getprop('coordinates', None)
    if coordinates is not None:
        
        # Split the list (allowing for incorrect comma separated
        # lists).
        for ncvar in re.split('\s+|\s*,\s*', coordinates):
            
            # Skip dimension coordinates which are in the list
            if ncvar in field_ncdimensions: #nc.variables[data_var].dimensions:
                continue

            # Skip auxiliary coordinates which are in the list but not
            # in the file
            if ncvar not in nc.variables:
                continue

            aux = f.space.new_auxiliary_key()

            # Set dimensions 
            aux_ncdimensions = _ncdimensions(nc.variables[ncvar], lama)
            dimensions = [ncdim_to_dim[ncdim] for ncdim in aux_ncdimensions
                          if ncdim in ncdim_to_dim]    

            f.space.dimensions[aux] = dimensions

            if ncvar in seen_in_file:
                coord = seen_in_file[ncvar].copy()
            else:
                coord = _create_Coordinate(nc, ncvar, attributes, f,
                                           lama=lama, key=aux)
                seen_in_file[ncvar] = coord
            #--- End: if
               
            # --------------------------------------------------------
            # Turn a ..
            # --------------------------------------------------------
            is_dimension_coordinate = False
            if not dimensions:
                if nc.variables[ncvar].dtype.kind is 'S':
                    # String valued scalar coordinate                        
                    dim = f.space.new_dimension(size=1)
                    f.space.dimensions[aux] = [dim]
                    ncvar_to_key[ncvar] = aux
                else:  
                    # Numeric valued scalar coordinate                           
                    is_dimension_coordinate = True
            #--- End: if

            if is_dimension_coordinate:
                # Insert dimension coordinate
                dim = f.space.insert_dim_coordinate(coord, copy=False)
                f.space.nc_dimensions[dim]= ncvar
                ncvar_to_key[ncvar] = dim

            else:
                f.space.insert_aux_coordinate(coord, key=aux,
                                              dimensions=dimensions, copy=False)
                ncvar_to_key[ncvar] = aux
        #--- End: for

        f.delprop('coordinates')
    #--- End: if

    # Make sure that the coordinates start with no transforms
    for coord in f.space.itercoordinates(): 
        if hasattr(coord, 'transforms'):
            del coord.transforms
    #--- End: for

    # ----------------------------------------------------------------
    # Add formula_terms to coordinates' Transform attributes
    # ----------------------------------------------------------------
    for coord in f.space.itercoordinates():
        coord_ncvar = coord.ncvar

        if coord_ncvar in formula_terms:
            # This coordinate's formula_terms transform has already
            # been created, so just copy it. (We have to do this
            # because the 'formula_terms' public attribute on the
            # coordinate has been deleted.)
            transform_id = f.space.insert_transform(formula_terms[coord_ncvar])
 
            if hasattr(coord, 'transforms'):
                coord.transforms.append(transform_id)
            else:
                coord.transforms = [transform_id]
            
            continue
        #-- End: if

        # Still here?
        form_terms = coord.getprop('formula_terms', None)
        if form_terms is None:
            # This coordinate doesn't have a formula_terms attribute
            continue

        # Still here? Then this coordinate has a formula_terms
        # transform that needs creating
        transform = Transform(standard_name=coord.standard_name)
        
        # Add the equation terms and references to their values to to
        # new auxiliary coordinate's transform.
        ft = re.split('\s+|\s*:\s+', form_terms)
        for term, ncvar in zip(ft[0::2], ft[1::2]):

            if ncvar in ncvar_to_key:
                # CASE 1: This variable is a coordinate of the field,
                #         so we point to it from the transform.
                value = ncvar_to_key[ncvar]

            elif 'bounds' not in attributes[ncvar]:
                # CASE 2: This variable is not a coordinate of the
                #         field and it has no bounds, so it goes in to
                #         the transform as an independent field.
                value = {'ncvar': ncvar}
                
            else:
                # CASE 3: This variable is not a coordinate of the
                #         field and it has bounds
                aux_dims = []
                var_ncdimensions = _ncdimensions(nc.variables[ncvar], lama)
                for ncdim in var_ncdimensions: #nc.variables[ncvar].dimensions:
                    if ncdim not in ncdim_to_dim:
                        aux_dims = None
                        break

                    aux_dims.append(ncdim_to_dim[ncdim])
                #--- End: for

                if aux_dims is None:
                    # CASE 3a: This variable's dimensions are not a
                    #          subset of the field's dimensions.
                    #          Therefore it goes into the transform as
                    #          an independent field with the bounds
                    #          deleted.
                    value = {'ncvar': ncvar}

                else:
                    # CASE 3b: This variable's dimensions are a subset
                    #          of the field's dimensions. Therefore
                    #          add it to the field as an auxiliary
                    #          coordinate and point to it from the
                    #          transform.
                    aux = f.space.new_auxiliary_key()
                    coord = _create_Coordinate(nc, ncvar, attributes, f, lama=lama, key=aux)
                    if coord.hasprop('formula_terms'):
                        coord.delprop('formula_terms')

                    aux = f.space.insert_aux_coordinate(coord, dimensions=aux_dims,
                                                        key=aux, copy=False)

                    ncvar_to_key[ncvar] = aux

#                    aux = 'aux%(auxN)d' % locals()##
#                    auxN += #1
                    
#                    f.space.dimensions[aux] = aux_dims
#                    f.space[aux] = _create_Coordinate(nc, ncvar, attributes)
#                    if f.space[aux].hasprop('formula_terms'):
#                        f.space[aux].delprop('formula_terms')
                    
                    # Add this variable name and its bounds name to
                    # the set of variables which are not fields. Note
                    # that if it turns out that this variable is also
                    # being used as a field in another transform then
                    # its name will be removed from this set.
                    not_fields.update((ncvar, attributes[ncvar]['bounds']))
                    
                    value = aux
            #--- End: if
                    
            transform[term] = value
        #--- End: for 

        transform_id = f.space.insert_transform(transform, copy=False)

        if hasattr(coord, 'transforms'):
            coord.transforms.append(transform_id)
        else:
            coord.transforms = [transform_id]

        formula_terms[coord_ncvar] = transform

        if coord.hasprop('formula_terms'):
            coord.delprop('formula_terms')
    #--- End: for

    # ----------------------------------------------------------------
    # Add grid mapping to coordinates' Transform attributes
    # ----------------------------------------------------------------
    grid_mapping = f.getprop('grid_mapping', None)

    if grid_mapping is not None:

        for ncvar in grid_mapping.split():

            if ncvar not in attributes:
                continue

            if 'grid_mapping_name' not in attributes[ncvar]:
                attributes[ncvar]['grid_mapping_name'] = ncvar

            grid_mapping_name = attributes[ncvar]['grid_mapping_name']

          #  transform_id = 'trans%(transN)d' % locals()
          #  transN += 1     

#            f.space.transforms[transform_id] = Transform(**attributes[ncvar])
 #           f.space.transforms[transform_id].ncvar = ncvar
#            f.space.transforms[transform_id].name         = grid_mapping_name
#            f.space.transforms[transform_id].grid_mapping = True

            transform = Transform(**attributes[ncvar])
            transform.ncvar = ncvar

            transform_id = f.space.insert_transform(transform, copy=False)

            # Work out which coordinates need the transform attached
            # to them
            for coord in f.space.itercoordinates():
                coord_name = coord.name()

                if coord_name in ('longitude', 'latitude'):
                    if hasattr(coord, 'transforms'):
                        coord.transforms.append(transform_id)
                    else:
                        coord.transforms = [transform_id]
                    continue
                #--- End: if

                if grid_mapping_name == 'rotated_latitude_longitude':
                    if coord_name in ('grid_longitude', 'grid_latitude'):
                        if hasattr(coord, 'transforms'):
                            coord.transforms.append(transform_id)
                        else:
                            coord.transforms = [transform_id]

                elif coord_name in ('projection_x_coordinate', 
                                    'projection_y_coordinate'):
                    if hasattr(coord, 'transforms'):
                        coord.transforms.append(transform_id)
                    else:
                        coord.transforms = [transform_id]
            #--- End: for
        #--- End: for

        f.delprop('grid_mapping')
    #--- End: try

    # ----------------------------------------------------------------
    # Add cell measures to the field
    # ----------------------------------------------------------------
    cell_measures = f.getprop('cell_measures', None)

    if cell_measures is not None:

        # Parse the cell measures attribute
        cell_measures = re.split('\s*(\w+):\s*', cell_measures)
        
        for measure, ncvar in zip(cell_measures[1::2], 
                                  cell_measures[2::2]):

            if ncvar not in attributes:
                continue

            cm = f.space.new_cell_measure_key()

            # Set cell measures' dimensions 
            cm_ncdimensions = _ncdimensions(nc.variables[ncvar], lama)
            dimensions = [ncdim_to_dim[ncdim] for ncdim in cm_ncdimensions]
            f.space.dimensions[cm] = dimensions

            if ncvar in seen_in_file:
                # Copy the cell measure as it already exists
                cell = seen_in_file[ncvar].copy()
            else:
                cell = _create_CellMeasure(nc, ncvar, attributes, f,
                                           lama=lama, key=cm)            
                cell.measure = measure
                seen_in_file[ncvar] = cell
            #--- End: if

            f.space.insert_cell_measure(cell, key=cm, dimensions=dimensions,
                                        copy=False)
        #--- End: for

        f.delprop('cell_measures')
    #--- End: if

    # -----------------------------
    # Add cell methods to the field
    # -----------------------------
    if cell_methods is not None:
        f.cell_methods = cell_methods.netCDF_translation(f)

    # ----------------------------------------------------------------
    # Parse an ancillary_variables string to a list of netCDF variable
    # names, which will get converted to an AncillaryVariables object
    # later. Add these netCDF variable names to the set of all
    # ancillary data variables in the file.
    # ----------------------------------------------------------------
    if hasattr(f, 'ancillary_variables'):
        f.ancillary_variables = f.ancillary_variables.split()
        ancillary_variables.update(f.ancillary_variables)
    #--- End: if

    # 

#    f.finalize()

    # Return the finished field
    return f
#--- End: def

def _create_Coordinate(nc, ncvar, attributes, f, lama=False, key=None):
    '''

Create a coordinate variable, including any bounds.

:Parameters:

    nc : netCDF4.Dataset
        The entire netCDF file in a `netCDF4.Dataset` instance.

    ncvar : str
        The netCDF name of the coordinate variable.

    attributes : dict
        Dictionary of the coordinate variable's netCDF attributes.

:Returns:

    out : Coordinate
        The new coordinate.

'''
    c = Coordinate(attributes[ncvar])

    c.ncvar = ncvar
    
#    v = nc.variables[ncvar]
#    data = NetCDFFileArray(_filename=filename, _ncvar=ncvar,
#                          dtype=v.dtype, ndim=v.ndim, shape=v.shape,
#                           size=v.size)
#
#    # ------------------------------------------------------------
#    # Collapse a string valued auxiliary
#    # coordinate. E.g. ['a','b','c'] becomes ['abc']
#    # ------------------------------------------------------------
#    if data.dtype.kind is 'S':
#        new_shape = data.shape[0:-1]
#        new_size  = long(reduce(mul, new_shape, 1))
#        strlen    = data.shape[-1]
#
#        data = numpy.ma.resize(data[...], (new_size, strlen))
#        
#        # Make sure everything is ok if data is a masked array
#        try:
#            data.set_fill_value(' ')
#            data = data.filled()
#        except AttributeError:
#            pass
#
#        temp = [''.join(x).rstrip() for x in data]
#        data = numpy.ma.array(temp, dtype='|S%(strlen)d' % locals())
#
#        data = numpy.ma.resize(data, new_shape)
#
#        del temp
#    #--- End: if

#    if 'units' not in attributes[ncvar]:
#        c.Units = Units()

#    c.Data = Data(data=data, units=c.Units, _FillValue=c._FillValue)
    c.Data = _set_Data(nc.variables[ncvar], f, c, lama=lama, key=key)

    # ------------------------------------------------------------
    # Add any bounds
    # ------------------------------------------------------------
    if hasattr(c, 'climatology'):
        c.bounds      = c.climatology
        c.climatology = True
    #--- End: if

    if hasattr(c, 'bounds'):
        ncbounds = c.bounds

        b = CoordinateBounds(attributes[ncbounds])

        b.ncvar = ncbounds

        if 'units' in attributes[ncbounds]:
            calendar = attributes[ncbounds].get('calendar',
                                                getattr(c, 'calendar', None))
            b.Units = Units(attributes[ncbounds]['units'], calendar)
            if not units.equivalent(c.Units):
                print("WARNING: Overriding incompatible bounds units: %s (%s)" %
                      (repr(units), repr(c.Units)))
                b.Units = c.Units
            #--- End: if
        else:
            b.Units = c.Units

#        v = nc.variables[ncbounds]
#        data = NetCDFFileArray(_filename=filename, _ncvar=ncbounds, 
#                               dtype=v.dtype, ndim=v.ndim, shape=v.shape,
#                               size=v.size)
    #        b.Data = Data(data=data, units=units, _FillValue=b._FillValue)
        b.Data = _set_Data(nc.variables[ncbounds], f, b, lama=lama, key=key)

        # Ignore formula_terms attributes on coordinate bounds
        # variables
        if b.hasprop('formula_terms'):
            b.delprop('formula_terms')

        c.bounds = b
    #--- End: if
    
    # ---------------------------------------------------------
    # Return the coordinate
    # ---------------------------------------------------------
    return c
#--- End: def

def _create_CellMeasure(nc, ncvar, attributes, f, lama=False, key=None):
    '''

Create a cell measure variable.

:Parameters:

    nc : netCDF4.Dataset
        The entire netCDF file in a `netCDF4.Dataset` instance.

    ncvar : str
        The netCDF name of the cell measure variable.

    attributes : dict
        Dictionary of the cell measure variable's netCDF attributes.

:Returns:

    out : CellMeasure
        The new cell measure.

'''
    cm       = CellMeasure(attributes[ncvar])
    cm.ncvar = ncvar

#    if 'units' not in attributes[ncvar]:
#        cm.Units = Units()

#    v = nc.variables[ncvar]
#    data = NetCDFFileArray(_filename=filename, _ncvar=ncvar,
#                           dtype=v.dtype, ndim=v.ndim, shape=v.shape,
#                           size=v.size)
#    cm.Data = Data(data=data, units=cm.Units, _FillValue=cm._FillValue)
    cm.Data = _set_Data(nc.variables[ncvar], f, cm, lama=lama, key=key)

    return cm
#--- End: def

def _abspath(filename):
    '''

'''
    if filename.startswith('http://'):
        return filename

    return os_path_abspath(filename)
#--- End: def

def _ncdimensions(ncvar, lama=False):
    '''

:Parameters:

    ncvar : netCDF4.Variable

    lama : bool, optional

:Returns:

    None

**Examples** 

'''               
    if lama and 'LAMA_dimensions' in ncvar.ncattrs():
        return ncvar.getncattr('LAMA_dimensions').split()
    
    return map(str, ncvar.dimensions)
#--- End: def

def _set_Data(ncvar, f, variable, lama=False, key=None):
    '''

:Parameters:

    ncvar : netCDF4.Variable

    f : Field

    variable : Variable

    lama : bool, optional

    key : str, optional

:Returns:

    None

**Examples** 

'''

    isstring_valued = (ncvar.dtype.kind is 'S' and 
                       ncvar.ndim > 1          and
                       ncvar.shape[-1] > 1)

    has_LAMA_data = variable.hasprop('LAMA_data')
 
    if lama and has_LAMA_data:
        LAMA_data = variable.getprop('LAMA_data')

        if isstring_valued:
            strlen = f.space.dimensions[key].pop()
            f.space.dimension_sizes.pop(strlen, None)
        #--- End: if

        shape = [f.space.dimension_sizes[dim]
                 for dim in f.space.dimensions[key]]

        dimensions = variable.getprop('LAMA_dimensions').split()

        data = Data(json=LAMA_data, shape=shape, dimensions=dimensions,
                    dtype=ncvar.dtype, _filename=f.file,
                    units=variable.Units, _FillValue=variable._FillValue)

        variable.delprop('LAMA_data')
        variable.delprop('LAMA_dimensions')
       
    else:
        ndim  = ncvar.ndim
        dtype = ncvar.dtype
        shape = ncvar.shape
        size  = ncvar.size
        
        if not isstring_valued:
            shape = ncvar.shape
        else:
            ndim -= 1
            dtype = numpy.dtype('S%d' % shape[-1])
            shape = shape[0:-1]
            size  = long(reduce(mul, shape, 1))
        #--- End: if
            
        filearray = NetCDFFileArray(_filename=f.file, _ncvar=ncvar._name,
                                    dtype=dtype, ndim=ndim, shape=shape,
                                    size=size)
        
        data = Data(data=filearray,
                    units=variable.Units, _FillValue=variable._FillValue)
    #--- End: if

    return data
#--- End: def

def _is_lama_variable(ncvar, lama):
    '''

Return True if a netCDF variable is a LAMA variable.

:Parameters:

    ncvar : netCDF4.Variable

    lama : bool

:Returns:

    out : bool

**Examples** 

'''
    return lama and 'LAMA_variable' in ncvar.ncattrs()
#--- End: def
        
